using System;
using System.Collections.Generic;
using System.Text;

using SlimDX.Direct3D9;
using Barrage.Shaders;
using Barrage.Textures;
using Barrage;
using SlimDX;
using Barrage.Global;
using Barrage.Global.Vertex;

using NLog;
using Spacedash.Logic.Scenario;
using Spacedash.Logic.Scenario.Cells;
using Spacedash.Logic;

namespace Spacedash.Game.Meshes
{
    struct DrawPart
    {
        public int Begin;
        public int Count;
    }
    class ScenarioMesh : Barrage.Mesh
    {
        const int PART_SIZE = 6 * 250;
        protected SlimDX.Direct3D9.VertexBuffer planeBuffer = null;
        protected SlimDX.Direct3D9.VertexBuffer colorBuffer = null;
        protected SlimDX.Direct3D9.IndexBuffer indexBuffer = null;

        protected VertexColorOnly[] colors = null;

        protected Scenario scenario;
        public Scenario Scenario { get { return scenario; } }

        protected int triangleNumber;

        protected VertexDeclaration decl;
        
        protected List<DrawPart> drawParts = new List<DrawPart>();
        protected List<BoundingSphere> boundingSpheres = new List<BoundingSphere>();


        protected void initDeclaration()
        {

            VertexElement[] scenarioElements = VertexPositionUVNormal.Elements;
            VertexElement[] colorElements = VertexColorOnly.Elements;
            for (int i=0;i<colorElements.Length-1;i++)
                colorElements[i].Stream = 1;

            VertexElement[] combinedElements = new VertexElement[scenarioElements.Length - 1 + colorElements.Length];
            Array.Copy(scenarioElements, combinedElements, scenarioElements.Length - 1);
            Array.Copy(colorElements, 0, combinedElements, scenarioElements.Length - 1, colorElements.Length);

            decl = new VertexDeclaration(Engine.Device, combinedElements);

        }

        protected void initializeBuffer()
        {
            //int vertexNumber = 6 * width * length;
            int vertexNumber = (4 * scenario.ColumnNumber ) * scenario.RowNumber;
            float perimeter = scenario.RowNumber * scenario.CellSize.Y;
            float fPI = (float)Math.PI;
            float radius = perimeter / (2.0f * fPI);
            float lateralRadius = scenario.CellSize.X * scenario.ColumnNumber / 2.0f;

            triangleNumber = 2 * scenario.ColumnNumber * scenario.RowNumber;

            initDeclaration();

            planeBuffer = new VertexBuffer(Engine.Device, VertexPositionUVNormal.SizeBytes * vertexNumber, Usage.WriteOnly, VertexFormat.None, Pool.Managed);
            
            colorBuffer = new VertexBuffer(Engine.Device, VertexColorOnly.SizeBytes * vertexNumber, Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default);
            colors = new VertexColorOnly[vertexNumber];
            indexBuffer = new IndexBuffer(Engine.Device, 4 * 3 * triangleNumber, Usage.WriteOnly, Pool.Managed, false);
            
            int[] indices = new int[3 * triangleNumber];
            VertexPositionUVNormal []vertices=new VertexPositionUVNormal[vertexNumber];
            Random r=new Random();
            for (int i = 0; i < scenario.RowNumber; i++)
            {
                float NegSin = (float)Math.Sin(fPI * 2 * i / (float)scenario.RowNumber);
                float PosSin = (float)Math.Sin(fPI * 2 * (i + 1) / (float)scenario.RowNumber);
                float NegCos = (float)-Math.Cos(fPI * 2 * i / (float)scenario.RowNumber);
                float PosCos = (float)-Math.Cos(fPI * 2 * (i + 1) / (float)scenario.RowNumber);
                for (int j = 0; j < scenario.ColumnNumber; j++)
                {
                    int indexBufferIndex = (i * scenario.ColumnNumber + j) * 6;
                    int baseIndex = (i * scenario.ColumnNumber + j) * 4;

                    float NegAngle = scenario.MaxSideAngle * ((j / (float)scenario.ColumnNumber) * 2 - 1);
                    float PosAngle = scenario.MaxSideAngle * (((j + 1) / (float)scenario.ColumnNumber) * 2 - 1);

                    float NegXCos = (float)Math.Cos(NegAngle - fPI / 2.0f);
                    float PosXCos = (float)Math.Cos(PosAngle - fPI / 2.0f);
                    float NegX = NegXCos * lateralRadius;
                    float PosX = PosXCos * lateralRadius;

                    float NegHeight = (float)Math.Sin(NegAngle - fPI / 2.0f) * lateralRadius + lateralRadius;
                    float PosHeight = (float)Math.Sin(PosAngle - fPI / 2.0f) * lateralRadius + lateralRadius;

                    float NegHeightY1 = - NegCos * NegHeight;
                    float NegHeightY2 = -PosCos * NegHeight;
                    float PosHeightY1 = -PosCos * PosHeight;
                    float PosHeightY2 = -NegCos * PosHeight;
                    float NegHeightZ1 = -NegSin * NegHeight;
                    float NegHeightZ2 = -PosSin * NegHeight;
                    float PosHeightZ1 = -PosSin * PosHeight;
                    float PosHeightZ2 = -NegSin * PosHeight;

                    float NegHeightPosY = -PosCos * NegHeight;
                    float PosHeightY = -PosCos * PosHeight;
                    float NegHeightZ = -NegSin * NegHeight;
                    float PosHeightZ = -PosSin * PosHeight;

                    float NegZ = NegSin * radius;
                    float PosZ = PosSin * radius;
                    float NegY = NegCos * radius;
                    float PosY = PosCos * radius;

                    vertices[baseIndex].Position = new Vector3(NegX, NegY + NegHeightY1 + radius, NegZ + NegHeightZ1);
                    vertices[baseIndex + 1].Position = new Vector3(NegX, PosY + NegHeightY2 + radius, PosZ + NegHeightZ2);
                    
                    vertices[baseIndex].Normal = new Vector3(-NegXCos, -NegCos, -NegSin);
                    vertices[baseIndex + 1].Normal = new Vector3(-NegXCos, -PosCos, -PosSin);

                    vertices[baseIndex].UV = new Vector2(j * scenario.TileSize.X, i * scenario.TileSize.Y);
                    vertices[baseIndex + 1].UV = new Vector2(j * scenario.TileSize.X, (i + 1) * scenario.TileSize.Y);

                    vertices[baseIndex + 2].Position = new Vector3(PosX, NegY + PosHeightY2 + radius, NegZ + PosHeightZ2);
                    vertices[baseIndex + 3].Position = new Vector3(PosX, PosY + PosHeightY1 + radius, PosZ + PosHeightZ1);

                    vertices[baseIndex + 2].Normal = new Vector3(-PosXCos, -NegCos, -NegSin);
                    vertices[baseIndex + 3].Normal = new Vector3(-PosXCos, -PosCos, -PosSin);

                    vertices[baseIndex + 2].UV = new Vector2((j + 1) * scenario.TileSize.X, i * scenario.TileSize.Y);
                    vertices[baseIndex + 3].UV = new Vector2((j + 1) * scenario.TileSize.X, (i + 1) * scenario.TileSize.Y);
                    
                    indices[indexBufferIndex] = baseIndex;
                    indices[indexBufferIndex + 1] = baseIndex + 1;
                    indices[indexBufferIndex + 2] = baseIndex + 2;
                    indices[indexBufferIndex + 3] = baseIndex + 2;
                    indices[indexBufferIndex + 4] = baseIndex + 1;
                    indices[indexBufferIndex + 5] = baseIndex + 3;

                }
            }

            Vector3[] boundingBoxVertices = new Vector3[vertexNumber];
            for (int k = 0; k < vertices.Length; k++)
                boundingBoxVertices[k] = vertices[k].Position;

            DataStream stream = planeBuffer.Lock(0, 0, LockFlags.None);
            DataStream indexStream = indexBuffer.Lock(0, 0, LockFlags.None);
            
            indexStream.WriteRange(indices);
            stream.WriteRange(vertices);
            planeBuffer.Unlock();
            indexBuffer.Unlock();

            UpdateColors();

            // Create draw parts
            int begin = 0;
            int remaining = 3 * triangleNumber;
            while (remaining > 0)
            {
                DrawPart p = new DrawPart();
                p.Begin = begin;
                p.Count = Math.Min(remaining, PART_SIZE);
                drawParts.Add(p);
                Vector3[] bbVertices = new Vector3[indices[p.Begin + p.Count - 1] - indices[p.Begin]];
                Array.Copy(boundingBoxVertices, indices[p.Begin], bbVertices, 0, indices[p.Begin + p.Count-1] - indices[p.Begin]);
                boundingSpheres.Add(BoundingSphere.FromPoints(bbVertices));
                
                begin += p.Count;
                remaining -= p.Count;
            }
        }

        public void UpdateColors()
        {
            if (scenario.ChangedCells.Count == 0)
                return;
            foreach (Cell cell in scenario.ChangedCells)
            {
                int baseIndex = (cell.Row * scenario.ColumnNumber + cell.Column) * 4;
                Color4 c = new Color4(cell.Color);
                colors[baseIndex].Color = c;
                colors[baseIndex + 1].Color = c;
                colors[baseIndex + 2].Color = c;
                colors[baseIndex + 3].Color = c;
            }
            DataStream colorStream = colorBuffer.Lock(0, 0, LockFlags.Discard);
            colorStream.WriteRange(colors);
            colorBuffer.Unlock();
        }

        public ScenarioMesh(Scenario scenario,BaseTexture source)
        {
            this.scenario = scenario;
            initializeBuffer();
            shader = new D3DShader("Scenario.fx");
            Barrage.Material m = new Barrage.Material();
            m.Ambient = new Color4(1, 0.05f, 0.05f, 0.05f);
            //m.Texture = source;
            m.Textures.Add(TextureManager.Textures["cellshading.dds"]);
            m.Textures.Add(TextureManager.Textures["GroundMark.png"]);
            m.Textures.Add(TextureManager.Textures["Shadow.jpg"]);
            m.Textures.Add(TextureManager.Textures["Shadow.jpg"]);
            m.Textures.Add(TextureManager.Textures["Shadow.jpg"]);
            m.Textures.Add(TextureManager.Textures["Shadow.jpg"]);
            m.MatrixParameters.Add(Matrix.Identity);
            m.MatrixParameters.Add(Matrix.Identity);
            m.MatrixParameters.Add(Matrix.Identity);
            m.MatrixParameters.Add(Matrix.Identity);
            materials.Add(m);
        }

        public override void Render(Item item)
        {
            shader.BeginRender(item, materials[0]);
            Engine.Device.VertexDeclaration = decl;
            Engine.Device.SetRenderState(RenderState.CullMode, Cull.Counterclockwise);
            
            Engine.Device.SetStreamSource(0, planeBuffer, 0, VertexPositionUVNormal.SizeBytes);
            Engine.Device.SetStreamSource(1, colorBuffer, 0, VertexColorOnly.SizeBytes);
            Engine.Device.Indices = indexBuffer;

            int passNumber = shader.PassNumber(materials[0]);
            for (int j = 0; j < passNumber; j++)
            {
                shader.BeginPass(j);
                for (int i = 0; i < drawParts.Count; i++)
                {
                    RenderStats.RenderCalls++;
                    if (Scene.CurrentInstance.Camera.CheckCull(boundingSpheres[i]))
                    {
                        RenderStats.CulledObjects++;
                        continue;
                    }
                    Engine.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, drawParts[i].Count, drawParts[i].Begin, drawParts[i].Count / 3); 

                }
                shader.EndPass();
            } 
            shader.EndRender();

            NLog.LogManager.GetLogger("Spacedash::Game::Meshes::ScenarioMesh").Debug("Rendering Scenario");
        }

        public override void Dispose()
        {
            drawParts.Clear();
            decl.Dispose();
            decl = null;
            boundingSpheres.Clear();
            planeBuffer.Dispose();
            planeBuffer = null;
            indexBuffer.Dispose();
            indexBuffer = null;
            colorBuffer.Dispose();
            colorBuffer = null;
            base.Dispose();
        }
    }
}
